#! /usr/bin/env python
# coding=utf-8
#================================================================
#   Copyright (C) 2019 * Ltd. All rights reserved.
#
#   Editor      : VIM
#   File name   : dataset.py
#   Author      : YunYang1994
#   Created date: 2019-03-15 18:05:03
#   Description :
#
#================================================================

import os
import cv2
import random
import numpy as np
import tensorflow as tf
import core.utils as utils
from core.config import cfg



class Dataset(object):
    """implement Dataset here"""
    def __init__(self, dataset_type):
        self.annot_path  = cfg.TRAIN.ANNOT_PATH if dataset_type == 'train' else cfg.TEST.ANNOT_PATH
        self.input_sizes = cfg.TRAIN.INPUT_SIZE if dataset_type == 'train' else cfg.TEST.INPUT_SIZE
        self.batch_size  = cfg.TRAIN.BATCH_SIZE if dataset_type == 'train' else cfg.TEST.BATCH_SIZE
        self.data_aug    = cfg.TRAIN.DATA_AUG   if dataset_type == 'train' else cfg.TEST.DATA_AUG
        # 请注意, 这里的输入尺寸有很多 [320, 352, 384, 416, 448, 480, 512, 544, 576, 608]
        # 需要随机选择一种输入图像的尺寸
        self.train_input_sizes = cfg.TRAIN.INPUT_SIZE
        # strides 原本是list类型,通过这个API变成Numpy 类型
        self.strides = np.array(cfg.YOLO.STRIDES)
        self.classes = utils.read_class_names(cfg.YOLO.CLASSES)
        self.num_classes = len(self.classes)
        # anchors 是一个矩阵, 每一种尺度下都有3种anchor boxes 的长度比值
        self.anchors = np.array(utils.get_anchors(cfg.YOLO.ANCHORS))
        # 阐述每一种 上采样尺度下有三种不同长度的比值的anchor boxes
        self.anchor_per_scale = cfg.YOLO.ANCHOR_PER_SCALE
        # 150的含义是每一次batch 中标定框的数量不能超过150个
        self.max_bbox_per_scale = 150
        # 这里返回的 annotations 是一个列表, 列表元素有图片地址、坐标位置以及对应的ID的类
        self.annotations = self.load_annotations(dataset_type)
        # 得到一共有多少个样本，即多少张带bbox信息的图片
        self.num_samples = len(self.annotations)
        # np.ceil是向上取整,算出来是float64 ,然后强行变为整型
        # 这里计算出一共能训练多少个batch数量
        self.num_batchs = int(np.ceil(self.num_samples / self.batch_size))
        self.batch_count = 0


    def load_annotations(self, dataset_type):
        with open(self.annot_path, 'r') as f:
            txt = f.readlines()
            annotations = [line.strip() for line in txt if len(line.strip().split()[1:]) != 0]
        np.random.shuffle(annotations)
        return annotations
    '''
    for example:
    def load_annotations(annot_path):
        with open(annot_path, 'r') as f:
            txt = f.readlines()
            annotations = [line.strip() for line in txt if len(line.strip().split()[1:]) != 0]
            annotations_1 = [line.strip().split() for line in txt];
            print(annotations_1);
            print(type(annotations_1[0]));
        np.random.shuffle(annotations)
        return annotations
    
    annotations_path = 'F:/new/CSDN/实践项目/yolo_v3/tensorflow-yolov3/data/dataset/voc_train.txt';
    
    output:
    [['/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000005.jpg', 
    '263,211,324,339,8', '165,264,253,372,8', '241,194,295,299,8'], 
    ['/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000007.jpg', 
    '141,50,500,330,6'], ['/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000009.jpg', 
    '69,172,270,330,12', '150,141,229,284,14', '285,201,327,331,14', '258,198,297,329,14'], 
    ['/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000012.jpg', '156,97,351,270,6'], 
    ['/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000016.jpg', '92,72,305,473,1'], 
    ['/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000017.jpg', '185,62,279,199,14', '90,78,403,336,12'], 
    ['/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000019.jpg', '231,88,483,256,7', '11,113,266,259,7'], 
    ['/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000020.jpg', '33,148,371,416,6'], 
    ['/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000021.jpg', '1,235,182,388,11', '210,36,336,482,14', 
    '46,82,170,365,14', '11,181,142,419,14'], ['/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000023.jpg', 
    '9,230,245,500,1', '230,220,334,500,1', '2,1,117,369,14', '3,2,243,462,14', '225,1,334,486,14'], 
    ['/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000024.jpg', '196,165,489,247,18'], 
    ['/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000026.jpg', '90,125,337,212,6'], 
    ['/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000030.jpg', '36,205,180,289,1', '51,160,150,292,14', 
    '295,138,450,290,14'], ['/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000032.jpg', '104,78,375,183,0', 
    '133,88,197,123,0', '195,180,213,229,14', '26,189,44,238,14'], 
    ['/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000033.jpg', '9,107,499,263,0', '421,200,482,226,0', '325,188,411,223,0'], 
    ['/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000034.jpg', '116,167,360,400,18', '141,153,333,229,18'], 
    ['/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000035.jpg', '1,96,191,361,14', '218,98,465,318,14'], 
    ['/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000036.jpg', '27,79,319,344,11'], 
    ['/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000039.jpg', '156,89,344,279,19'], 
    ['/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000041.jpg', '363,47,432,107,19', '216,92,307,302,14', '164,148,227,244,14']]
    
    <class 'list'>
    
    ['/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000021.jpg 1,235,182,388,11 210,36,336,482,14 46,82,170,365,14 11,181,142,419,14', '/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000012.jpg 156,97,351,270,6', '/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000039.jpg 156,89,344,279,19', '/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000024.jpg 196,165,489,247,18', '/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000007.jpg 141,50,500,330,6', '/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000030.jpg 36,205,180,289,1 51,160,150,292,14 295,138,450,290,14', '/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000041.jpg 363,47,432,107,19 216,92,307,302,14 164,148,227,244,14', '/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000032.jpg 104,78,375,183,0 133,88,197,123,0 195,180,213,229,14 26,189,44,238,14', '/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000017.jpg 185,62,279,199,14 90,78,403,336,12', '/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000009.jpg 69,172,270,330,12 150,141,229,284,14 285,201,327,331,14 258,198,297,329,14', '/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000034.jpg 116,167,360,400,18 141,153,333,229,18', '/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000005.jpg 263,211,324,339,8 165,264,253,372,8 241,194,295,299,8', '/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000036.jpg 27,79,319,344,11', '/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000023.jpg 9,230,245,500,1 230,220,334,500,1 2,1,117,369,14 3,2,243,462,14 225,1,334,486,14', '/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000026.jpg 90,125,337,212,6', '/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000035.jpg 1,96,191,361,14 218,98,465,318,14', '/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000033.jpg 9,107,499,263,0 421,200,482,226,0 325,188,411,223,0', '/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000016.jpg 92,72,305,473,1', '/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000020.jpg 33,148,371,416,6', '/home/yang/test/VOC/train/VOCdevkit/VOC2007/JPEGImages/000019.jpg 231,88,483,256,7 11,113,266,259,7']
    
    <class 'list'>
    '''

    def __iter__(self):
        # __iter__(self) 是一个迭代器, 他需要返回一个实现 __next__(self) 的对象
        # 所以说它会自动调用  def __next__(self) 函数式
        return self

    def __next__(self):
        # 这里的 __next__(self) 的函数定义在python 语法中很特殊, 属于class 定义中是会自动运行的有点类似初始化
        # else 中有个 raise StopIteration 抛异常, 这个异常异常机制需要for 循环配合使用
        # 在正式使用的时候, 可以从 train.py 这个文件中 P144 行看到 pbar 是间接创建了 Dataset 这个类的实例对象
        # 这里是通过 循环 'for train_data in pbar:' 来加载数据, 每一次会通过迭代器来调用本函数, 每一次加载都是一个batch size
        # 一直是通过for 循环来触发迭代器, 直到条件 'self.batch_count < self.num_batchs:' 触发, 即超出样本数以后会执行else 中的异常, 然后会停止for 循环
        with tf.device('/cpu:0'):
            self.train_input_size = random.choice(self.train_input_sizes)
            self.train_output_sizes = self.train_input_size // self.strides

            batch_image = np.zeros((self.batch_size, self.train_input_size, self.train_input_size, 3))

            batch_label_sbbox = np.zeros((self.batch_size, self.train_output_sizes[0], self.train_output_sizes[0],
                                          self.anchor_per_scale, 5 + self.num_classes))
            batch_label_mbbox = np.zeros((self.batch_size, self.train_output_sizes[1], self.train_output_sizes[1],
                                          self.anchor_per_scale, 5 + self.num_classes))
            batch_label_lbbox = np.zeros((self.batch_size, self.train_output_sizes[2], self.train_output_sizes[2],
                                          self.anchor_per_scale, 5 + self.num_classes))

            batch_sbboxes = np.zeros((self.batch_size, self.max_bbox_per_scale, 4))
            batch_mbboxes = np.zeros((self.batch_size, self.max_bbox_per_scale, 4))
            batch_lbboxes = np.zeros((self.batch_size, self.max_bbox_per_scale, 4))

            num = 0
            # batch_count 是一个计数器, 初始为0;
            # num_batchs 是根据数据集中样本的数量除以每一轮的batch size然后得出的能训练多少轮
            if self.batch_count < self.num_batchs:
                # 这里的num 是用来记录每一轮batch_size中第几张图片的索引
                while num < self.batch_size:
                    # index 用来记录当前状态下已经遍历了多少张样本数
                    index = self.batch_count * self.batch_size + num
                    # 如果遍历的样本数大于数据集的样本总数, 那么就会触发 index - num_samples
                    # 要明白, 只要比总样本数大1, 就会触发, 因此基本上会把Index 重置为1;
                    if index >= self.num_samples:  index -= self.num_samples
                    # self.annotations 是一个双重列表结构, 它的元素依然是一个列表, 包含了每一个样本的所有信息包括图片地址, 位置坐标以及对应的分类编号
                    annotation = self.annotations[index];
                    # 这里的bboxes 是一个数组, 即一张图片中所有的标定框全在这个数组里
                    # 这里的 parse_annotation 函数中调用了 下面定义的三种图像的预处理方式了, 用来提高泛化效果
                    image, bboxes = self.parse_annotation(annotation)
                    label_sbbox, label_mbbox, label_lbbox, sbboxes, mbboxes, lbboxes = self.preprocess_true_boxes(bboxes)

                    batch_image[num, :, :, :] = image
                    batch_label_sbbox[num, :, :, :, :] = label_sbbox
                    batch_label_mbbox[num, :, :, :, :] = label_mbbox
                    batch_label_lbbox[num, :, :, :, :] = label_lbbox
                    batch_sbboxes[num, :, :] = sbboxes
                    batch_mbboxes[num, :, :] = mbboxes
                    batch_lbboxes[num, :, :] = lbboxes
                    num += 1
                self.batch_count += 1
                # 这是提取出来的一个Batch数据, 送入网络开始训练
                return batch_image, batch_label_sbbox, batch_label_mbbox, batch_label_lbbox, \
                       batch_sbboxes, batch_mbboxes, batch_lbboxes
            else:
                self.batch_count = 0
                np.random.shuffle(self.annotations)
                raise StopIteration

    def random_horizontal_flip(self, image, bboxes):
        # 在训练的时候做预处理, 在 'def parse_annotation(self, annotation):' 函数中进行
        if random.random() < 0.5:
            _, w, _ = image.shape
            image = image[:, ::-1, :]
            bboxes[:, [0,2]] = w - bboxes[:, [2,0]]

        return image, bboxes

    def random_crop(self, image, bboxes):
        # 在训练的时候做预处理, 在 'def parse_annotation(self, annotation):' 函数中进行
        if random.random() < 0.5:
            h, w, _ = image.shape
            max_bbox = np.concatenate([np.min(bboxes[:, 0:2], axis=0), np.max(bboxes[:, 2:4], axis=0)], axis=-1)

            max_l_trans = max_bbox[0]
            max_u_trans = max_bbox[1]
            max_r_trans = w - max_bbox[2]
            max_d_trans = h - max_bbox[3]

            crop_xmin = max(0, int(max_bbox[0] - random.uniform(0, max_l_trans)))
            crop_ymin = max(0, int(max_bbox[1] - random.uniform(0, max_u_trans)))
            crop_xmax = max(w, int(max_bbox[2] + random.uniform(0, max_r_trans)))
            crop_ymax = max(h, int(max_bbox[3] + random.uniform(0, max_d_trans)))

            image = image[crop_ymin : crop_ymax, crop_xmin : crop_xmax]

            bboxes[:, [0, 2]] = bboxes[:, [0, 2]] - crop_xmin
            bboxes[:, [1, 3]] = bboxes[:, [1, 3]] - crop_ymin

        return image, bboxes

    def random_translate(self, image, bboxes):
        # 在训练的时候做预处理, 在 'def parse_annotation(self, annotation):' 函数中进行
        if random.random() < 0.5:
            h, w, _ = image.shape
            max_bbox = np.concatenate([np.min(bboxes[:, 0:2], axis=0), np.max(bboxes[:, 2:4], axis=0)], axis=-1)

            max_l_trans = max_bbox[0]
            max_u_trans = max_bbox[1]
            max_r_trans = w - max_bbox[2]
            max_d_trans = h - max_bbox[3]

            tx = random.uniform(-(max_l_trans - 1), (max_r_trans - 1))
            ty = random.uniform(-(max_u_trans - 1), (max_d_trans - 1))

            M = np.array([[1, 0, tx], [0, 1, ty]])
            image = cv2.warpAffine(image, M, (w, h))

            bboxes[:, [0, 2]] = bboxes[:, [0, 2]] + tx
            bboxes[:, [1, 3]] = bboxes[:, [1, 3]] + ty

        return image, bboxes

    def parse_annotation(self, annotation):

        line = annotation.split()
        image_path = line[0]
        if not os.path.exists(image_path):
            raise KeyError("%s does not exist ... " %image_path)
        image = np.array(cv2.imread(image_path))
        # 这里是直接做了一个对应的列表map 即一个列表里面1，2，3……对应的是每个坐标数组
        bboxes = np.array([list(map(int, box.split(','))) for box in line[1:]])# bboxes <class 'numpy.ndarray'>
        '''
        for example: 
        line = '1,2,4,5,12 2,6,78,23,78 23,45,78,98,12';
        line_1 = line.split();
        print(line_1);
        bboxes = np.array([list(map(int, box.split(','))) for box in line_1]);
        print(bboxes);
        print(type(bboxes));
        print(bboxes[:, [0, 2]]);
        
        output :
        ['1,2,4,5,12', '2,6,78,23,78', '23,45,78,98,12']
        [[ 1  2  4  5 12]
         [ 2  6 78 23 78]
         [23 45 78 98 12]]
        <class 'numpy.ndarray'>
        [[ 1  4]
         [ 2 78]
         [23 78]]
        '''
        if self.data_aug:
            image, bboxes = self.random_horizontal_flip(np.copy(image), np.copy(bboxes))
            image, bboxes = self.random_crop(np.copy(image), np.copy(bboxes))
            image, bboxes = self.random_translate(np.copy(image), np.copy(bboxes))

        image, bboxes = utils.image_preporcess(np.copy(image), [self.train_input_size, self.train_input_size], np.copy(bboxes))
        return image, bboxes

    def bbox_iou(self, boxes1, boxes2):
        # boxes1 是真值, 只有一行数值
        boxes1 = np.array(boxes1)
        # boxes2 是其中一种尺度下的三种比值, 有三行数组
        boxes2 = np.array(boxes2)
        # 这里是计算面积
        boxes1_area = boxes1[..., 2] * boxes1[..., 3]
        boxes2_area = boxes2[..., 2] * boxes2[..., 3]
        # 请注意, 这里前两列的数值是中心点的坐标, 后两列是表示bbx 的长和宽
        # 这里的计算是要通过在中心点的坐标上减去或者加上 长度或宽度的 1/2, 所以拼接出来的是最小值起始点和最大值结束点的坐标
        boxes1 = np.concatenate([boxes1[..., :2] - boxes1[..., 2:] * 0.5,
                                boxes1[..., :2] + boxes1[..., 2:] * 0.5], axis=-1);
        # 这里参数axis = -1 是指按显示最后一个维度进行拼接, 这里拼接出来后最终是4列, 行数按照原先计算的矩阵的行数来定
        boxes2 = np.concatenate([boxes2[..., :2] - boxes2[..., 2:] * 0.5,
                                boxes2[..., :2] + boxes2[..., 2:] * 0.5], axis=-1);
        # 这里是比较起始点最大的一组数值
        # 请注意, 原本boxes1 只有一行数据, 经过这个比较（是用自己的仅有的一行和boxes2的三行分别进行比较）
        # 因此, 输出值 left_up 是三行的结果
        left_up = np.maximum(boxes1[..., :2], boxes2[..., :2]);
        # 这里是比较结束点最小的一组数值
        '''
        for example:
        left_up = np.maximum(bbox_xywh_scaled[0, :2], bbox_xywh_scaled[..., :2]);
        print(bbox_xywh_scaled[0, :2]);
        print(bbox_xywh_scaled[..., :2]);
        print(left_up); 
        
        output:
        [0.8125 1.75  ]
        
        [[0.8125   1.75    ]
         [0.40625  0.875   ]
         [0.203125 0.4375  ]]
         
        [[0.8125 1.75  ]
         [0.8125 1.75  ]
         [0.8125 1.75  ]]
        '''
        right_down = np.minimum(boxes1[..., 2:], boxes2[..., 2:]);
        # 这一步是为了过滤结束点一定要比起始点大, 保证它们的差值大于0;
        #  inter_section 的输出shape是 (3, 2)
        inter_section = np.maximum(right_down - left_up, 0.0)
        # inter_area.shape 的输出是 (3,)
        inter_area = inter_section[..., 0] * inter_section[..., 1]
        union_area = boxes1_area + boxes2_area - inter_area

        return inter_area / union_area

    # 这里的bboxes 每次是对应一张图片的所有标定框
    def preprocess_true_boxes(self, bboxes):
        # 这里的label 是一个list , 应该有3个numpy数组
        label = [np.zeros((self.train_output_sizes[i], self.train_output_sizes[i], self.anchor_per_scale,
                           5 + self.num_classes)) for i in range(3)]
        '''
        for example：
        import random
        strides = [8, 16, 32];
        anchor_per_scale = 3;
        num_classes = 80;
        train_input_sizes = [320, 352, 384, 416, 448, 480, 512, 544, 576, 608];
        choice_input = random.choice(train_input_sizes);
        #print(choice_input)
        train_output_sizes = choice_input // np.array(strides);
        print(np.array(strides))
        label = [np.zeros((train_output_sizes[i], train_output_sizes[i], anchor_per_scale,
                                   5 + num_classes)) for i in range(3)];
        
        print(label[0].shape, label[1].shape, label[2].shape);
        
        output:
        [ 8 16 32]
        (44, 44, 3, 85) (22, 22, 3, 85) (11, 11, 3, 85)
        '''
        # 一个list, 里面有三个数组
        bboxes_xywh = [np.zeros((self.max_bbox_per_scale, 4)) for _ in range(3)];
        # 用来计数, 每一种上采样尺度下的有多少个iou 是符合阈值的, 即 iou_mask = iou_scale > 0.3;
        # 只要iou_mask 中有一个True, bbox_count就会自动加 1 ;
        bbox_count = np.zeros((3,))

        for bbox in bboxes:
            bbox_coor = bbox[:4]
            bbox_class_ind = bbox[4]

            onehot = np.zeros(self.num_classes, dtype=np.float)
            onehot[bbox_class_ind] = 1.0
            # 前面一个self.num_classes 参数是表示shape, 后面一个参数是数组里面填写的数值
            uniform_distribution = np.full(self.num_classes, 1.0 / self.num_classes);
            deta = 0.01
            smooth_onehot = onehot * (1 - deta) + deta * uniform_distribution
            # 前面一个参数是为了求bbox的中心点的坐标, 后面参数是为了求bbox的边长
            # numpy 参数中 axis = -1 是指默认shape 的最后一个维度
            # bbox_xywh.shape = (4,);
            # bbox_xywh[np.newaxis, :].shape = (1, 4)
            bbox_xywh = np.concatenate([(bbox_coor[2:] + bbox_coor[:2]) * 0.5, bbox_coor[2:] - bbox_coor[:2]], axis=-1);
            # 相当于矩阵相乘, 除一个矩阵相当于乘以它的倒数
            # 四个坐标值对应不同的上采样比值进行分别计算
            '''
            bbox_xywh = [ 6.5 14.   5.  12. ]
            strides[:, np.newaxis] = [[ 8]
                                     [16]
                                     [32]]
            bbox_xywh_scaled = [[0.8125   1.75     0.625    1.5     ]
                                 [0.40625  0.875    0.3125   0.75    ]
                                 [0.203125 0.4375   0.15625  0.375   ]]
            '''
            bbox_xywh_scaled = 1.0 * bbox_xywh[np.newaxis, :] / self.strides[:, np.newaxis]

            iou = []
            exist_positive = False
            for i in range(3):
                anchors_xywh = np.zeros((self.anchor_per_scale, 4));
                # 这里注意, 一共三次循环,每次循环都要计算, 每次循环先把 bbox_xywh_scaled 数组中对应的一行的前两个数值（中心点位置）
                # 赋值给 anchors_xywh[:, 0:2] 也就是说这个数组的三行的前两位中心点位置均相同
                anchors_xywh[:, 0:2] = np.floor(bbox_xywh_scaled[i, 0:2]).astype(np.int32) + 0.5;
                # self.anchors[i]是通过先验用聚类算出的9个bboxes簇（边长）的中心点;
                # 这里的I 表示每一行,一行是一个型号的上采样, 上采样分为小、中、大, 这里借鉴了多尺度金字塔结构
                # 把 anchors_xywh[:, 2:4] 这个数组的后两个位置的三行数值均变为有聚类得到的该尺度的 长度与宽度的比值
                anchors_xywh[:, 2:4] = self.anchors[i];
                '''
                self.anchors[i]的输出: 3个数组, 这个3可理解为batch
                每个样本3行2列, 意思是每一种尺度下有3种不同比值的对应的长和宽
                [[[ 1.25     1.625  ]
                  [ 2.       3.75   ]
                  [ 4.125    2.875  ]]
                
                 [[ 1.875    3.8125 ]
                  [ 3.875    2.8125 ]
                  [ 3.6875   7.4375 ]]
                
                 [[ 3.625    2.8125 ]
                  [ 4.875    6.1875 ]
                  [11.65625 10.1875 ]]]
                '''

                # 每次先选择bbox_xywh_scaled 其中一行就是一种上采样的尺度下的坐标值（这个数值是从一张对应的bbox数组中取出的其中一个）
                # 这里的 bbox_xywh_scaled 是真值也就是Label ,只不过经过了对应采样倍率和偏移计算
                # 之所以会产生偏移是因为图像在与处理中先把长方形图像变成正方形图时用的padding所产生的偏移, padding的填充是256//2
                # 这里的 anchors_xywh 数组中存储的是每一种上采样尺度下对应的该尺度的 3种 长和宽的bboxes 比值
                iou_scale = self.bbox_iou(bbox_xywh_scaled[i][np.newaxis, :], anchors_xywh)
                iou.append(iou_scale);
                # print('iou_mask:', iou_mask);
                # 这里有三个组数据是因为每个尺度上采样有3种比值
                # 请注意这里的返回值, 是这样的形式, iou_mask:  [False  True  True]
                # 请注意一点, 这里的 iou_scale 的计算是用 真值在每一种尺度下的对应数值和 聚类得到的对应尺度下的3种比值做IOU计算
                # 如果三个返回值中有一个TRUE， 那说明该图片的此标定框属于这个尺度下的 维度是TRUE 的这个维度(该维度表示坐标) 并赋值
                # 如果三种尺度下都没有TRUE， 那就说明这个标定框离开聚类的中心点很远, 直接找出最大的IOU的索引
                # 算出该MAX_IOU 属于哪一种尺度下的第几种长宽比值(每一种尺度下的长宽都按顺序排列)
                # 那就把矩阵赋值给该维度下的矩阵(矩阵中的每个维度都有很明确的物理意义);
                # 通过聚类能使比值接近的标定框都稳定在相对应的矩阵通道上(维度), 这样有利于坐标位置的损失更容易收敛;
                iou_mask = iou_scale > 0.3
                # np.any 如果iou_mask中有 True
                # 即在这个3中比值中有一个是TRUE, 那么就认为该尺度下有合适的框, 就继续计算
                # 再强调一遍, 三个返回值是因为每一种尺度下有3种长和宽的比值
                if np.any(iou_mask):
                    # 这里的 xind, yind 应该是对应的笛卡尔坐标系的位置;
                    xind, yind = np.floor(bbox_xywh_scaled[i, 0:2]).astype(np.int32);
                    '''
                    self.anchor_per_scale = cfg.YOLO.ANCHOR_PER_SCALE = 3;
                    label = [np.zeros((self.train_output_sizes[i], self.train_output_sizes[i], self.anchor_per_scale,
                           5 + self.num_classes)) for i in range(3)]  
                    '''
                    # 可以发现, 这里iou_mask的位置是对应 self.anchor_per_scale 这个位置, iou_mask是iou的比值是否大于阈值的返回值
                    # 这里把 xind, yind 的位置互换了, 意思是因为图像中行数增加的方向是命名为x
                    # 而列数增加的方向命名为y, 他们的物理意义和笛卡尔中的x, y 正好互相对调了, 请注意
                    # label 这个列表的三个子元素, 每个元素是一个多维数组;
                    # label 的每个元素的前两个维度代表是图像的长和宽, 在创建的时候就已经用输入图像的尺寸除以步长得到输出图像的的尺寸来创建的的
                    label[i][yind, xind, iou_mask, :] = 0;
                    # bbox_xywh 是真值, 经过偏移和比例计算的坐标中心点的坐标和Bbox的长度, 没有除过步长（即不同尺度）
                    # 这里的赋值（yind, xind）意味着在输出的特征图上对应的坐标点这个位置是有object 的
                    # 特征图上的每个点只负责一个对象, 并进行赋值
                    label[i][yind, xind, iou_mask, 0:4] = bbox_xywh
                    '''
                    for example:
                    print('inter_area', inter_area);
                    print('=======---------=======');
                    yind = 1; xind = 2;
                    iou_mask = inter_area > 0.45;
                    label[1][yind, xind, iou_mask, :] = 5;
                    print(label[1].shape);
                    print(label[1]);
                    print('iou_mask:', iou_mask);
                    
                    output:
                    inter_area [0.76 0.48 0.33]
                    =======---------=======
                    (3, 3, 3, 10)
                    [[[[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
                       [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
                       [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]
                    
                      [[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
                       [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
                       [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]
                    
                      [[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
                       [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
                       [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]]
                    
                    
                     [[[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
                       [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
                       [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]
                    
                      [[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
                       [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
                       [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]
                    
                      [[5. 5. 5. 5. 5. 5. 5. 5. 5. 5.]
                       [5. 5. 5. 5. 5. 5. 5. 5. 5. 5.]
                       [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]]
                    
                    
                     [[[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
                       [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
                       [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]
                    
                      [[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
                       [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
                       [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]
                    
                      [[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
                       [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
                       [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]]]
                    iou_mask: [ True  True False]
                    =======---------=======
                    '''
                    # 通过上面的LOG你会发现这里的iou_mask很有趣, 这个判断是有效果的, 只有是True的那个矩阵才会被赋值
                    # 并且在矩阵中原来那个带bool型的<class 'numpy.ndarray'>会直接映射到矩阵的维度
                    '''
                    print(type(iou_mask));
                    print(iou_mask.dtype);
                    输出：(请结合上面的398行的LOG一起看)
                    <class 'numpy.ndarray'>
                    bool
                    '''
                    label[i][yind, xind, iou_mask, 4:5] = 1.0
                    label[i][yind, xind, iou_mask, 5:] = smooth_onehot

                    bbox_ind = int(bbox_count[i] % self.max_bbox_per_scale)
                    # 这里的 i 是指的三种上采样, 三种尺度（步长）的其中一种的索引
                    bboxes_xywh[i][bbox_ind, :4] = bbox_xywh
                    bbox_count[i] += 1

                    exist_positive = True
            # 如果一个图像的其中一个标定框在3种尺度下（上采样）, 通过了合适IOU, 那么 exist_positive = True
            # 只要通过至少一次就可, 就不执行下面的判断;
            # 也就是说一个标定框 在通过阈值的以后, 最终的label 肯定会是三种其中一个尺度会有数值, 而其他尺度会是0;
            # 如果三种尺度下没有一个能够满足阈值, 那么就执行如下函数
            if not exist_positive:
                # iou 是一个 list , reshape(-1) 是表示自动计算行数, 直接变成行向量
                # 这意味着如果要执行那么前面 iou 中的3个数组每个数组三个数值, 一共9个数值全部变成一个统一的行向量
                # axis = -1 是表示从最后一维取最大值, 这里list 中的元素是数组, 且是行向量
                # argmax这个API是取最大的, 返回值位置信息即最大数值是位于数组的第几个
                best_anchor_ind = np.argmax(np.array(iou).reshape(-1), axis=-1);
                # 这里的作用可以得到最终最大的IOU是出自于3种尺度中的哪一个;
                best_detect = int(best_anchor_ind / self.anchor_per_scale);
                # 这里的作用是可以知道在这个尺度中是属于大、中、小 哪个框
                best_anchor = int(best_anchor_ind % self.anchor_per_scale);
                # 赋值真值中 中心点的坐标值在相应的尺度下的, 进行地板除操作
                xind, yind = np.floor(bbox_xywh_scaled[best_detect, 0:2]).astype(np.int32);
                # 这里的label 是一个列表, 每个列表的子元素是一个矩阵,
                # 前两个维度表示的是一种尺度下的图像最后一层的feature map
                # 也就是说只有真值中对应的图像的中心点存在才会在对应feature map的矩阵中对应的坐标位置赋值
                label[best_detect][yind, xind, best_anchor, :] = 0
                label[best_detect][yind, xind, best_anchor, 0:4] = bbox_xywh
                label[best_detect][yind, xind, best_anchor, 4:5] = 1.0
                label[best_detect][yind, xind, best_anchor, 5:] = smooth_onehot

                bbox_ind = int(bbox_count[best_detect] % self.max_bbox_per_scale)
                bboxes_xywh[best_detect][bbox_ind, :4] = bbox_xywh
                bbox_count[best_detect] += 1
        # 这里的label 的性质和 bboxes_xywh 差不多, 只不过label 的内容更多
        # 里面包含了不止坐标数据, 还有类别的置信度, 以及某一种尺度下的 某一种框（每种尺度有三种框）
        label_sbbox, label_mbbox, label_lbbox = label;
        # bboxes_xywh 存放的是真值的坐标值, 这里的坐标值没有经过尺度的缩放, 只经过应为对图像预处理所产生的平移和缩放
        # bboxes_xywh 这个是拥有3个数组的列表, 每个列表代表一种尺度, 只有大于设定的阈值 的那一种尺度才会有赋值, 即非零赋值
        sbboxes, mbboxes, lbboxes = bboxes_xywh
        return label_sbbox, label_mbbox, label_lbbox, sbboxes, mbboxes, lbboxes

    def __len__(self):
        return self.num_batchs




